import numpy as np  # 导入NumPy库，用于进行高效的数值计算，特别是数组操作，通常简写为np。
import pandas as pd # 导入Pandas库，用于数据处理和分析，提供了DataFrame这一强大的数据结构，通常简写为pd。
import matplotlib.pyplot as plt # 导入Matplotlib的pyplot模块，是Python中最基础的绘图库，通常简写为plt。
import seaborn as sns # 导入Seaborn库，它基于Matplotlib，提供了更高级、更美观的统计图表，通常简写为sns。
import warnings # 导入warnings模块，用于控制警告信息的输出。
from scipy import stats # 从SciPy库中导入stats模块，它包含了大量的统计函数和检验方法。
from sklearn.preprocessing import StandardScaler # 从Scikit-learn库中导入StandardScaler，用于数据标准化处理。
import plotly.express as px # 导入Plotly Express库，用于创建交互式图表，通常简写为px。
import plotly.graph_objects as go # 导入Plotly的graph_objects模块，用于构建更复杂的自定义交互式图表。
from plotly.subplots import make_subplots # 从Plotly中导入make_subplots，用于创建包含多个子图的交互式图表。

# 忽略警告
warnings.filterwarnings('ignore') # 设置警告过滤器，'ignore'参数表示忽略所有警告信息，让输出更整洁。

# 设置绘图风格 (移除中文字体设置)
sns.set_style("whitegrid") # 使用Seaborn设置图表的默认风格为"whitegrid"，即带有白色网格背景的样式。
sns.set_palette("husl") # 使用Seaborn设置图表的默认调色板为"husl"，提供了一套颜色鲜艳且分布均匀的色彩方案。

# 数据加载
data = pd.read_csv(r"diabetes_data.csv") # 使用Pandas的read_csv函数读取指定路径下的CSV文件，并将其内容加载到一个名为'data'的DataFrame对象中。

print("=" * 60) # 打印60个等号，作为报告标题的装饰线。
print("📊 糖尿病数据集 - 探索性数据分析报告") # 打印报告的主标题。

# ==================== 第一部分：数据基本信息 ====================
print("\n🔍 1. 数据基本信息") # 打印第一部分的标题。
print("-" * 40) # 打印40个短横线，作为分隔符。
print(f"数据集形状: {data.shape}") # 使用f-string格式化输出，打印数据集的形状（行数, 列数）。data.shape返回一个元组。
print(f"特征数量: {data.shape[1] - 1}") # 打印特征的数量。总列数（data.shape[1]）减去1个目标变量列。
print(f"样本数量: {data.shape[0]}") # 打印样本的数量，即数据集的行数（data.shape[0]）。

print(f"\n📋 数据集信息:") # 打印"数据集信息"的子标题。
print(data.info()) # 调用DataFrame的info()方法，它会打印出一个简洁的摘要，包括索引类型、列名、非空值数量和内存使用情况。

print(f"\n👀 前5行数据:") # 打印"前5行数据"的子标题。
print(data.head()) # 调用head()方法，默认显示DataFrame的前5行，用于快速预览数据内容。

print(f"\n🔍 缺失值检查:") # 打印"缺失值检查"的子标题。
missing_values = data.isnull().sum() # data.isnull()将每个单元格的值转为True（如果是缺失值）或False。.sum()会按列求和，计算每列的缺失值总数。
print(missing_values) # 打印每列的缺失值数量。
if missing_values.sum() == 0: # 检查所有列的缺失值总和是否为0。
    print("✅ 数据集无缺失值") # 如果总和为0，则打印无缺失值的确认信息。
else: # 否则
    print("❌ 发现缺失值，需要处理") # 打印发现缺失值的警告信息。

print(f"\n📊 数据类型分布:") # 打印"数据类型分布"的子标题。
print(data.dtypes.value_counts()) # data.dtypes返回每列的数据类型。.value_counts()统计这些数据类型出现的次数。

# ==================== 第二部分：目标变量分析 ====================
print("\n🎯 2. 目标变量分析") # 打印第二部分的标题。
print("-" * 40) # 打印分隔符。

# 目标变量分布
target_counts = data['Diagnosis'].value_counts() # 对'Diagnosis'列进行计数，统计每个唯一值（0和1）出现的次数。
target_pct = data['Diagnosis'].value_counts(normalize=True) * 100# normalize=True使value_counts()返回频率而非次数，乘以100得到百分比。

print("目标变量分布:") # 打印子标题。
for i in range(len(target_counts)): # 遍历目标变量的两个类别。
    if target_counts.index[i] == 0:
        label = "无糖尿病"
    else:
        label = "有糖尿病" # 根据索引值（0或1）确定标签文本。
    print(f"  {label} (Diagnosis={target_counts.index[i]}): {target_counts.iloc[i]} 个样本 ({target_pct.iloc[i]:.1f}%)") # 格式化输出每个类别的样本数和百分比。

# 可视化目标变量分布
fig, axes = plt.subplots(1, 3, figsize=(18, 5)) # 创建一个包含1行3列的子图画布，总尺寸为18x5英寸。fig是整个画布，axes是包含3个子图对象的数组。

# 柱状图
colors = ['#2ecc71', '#e74c3c'] # 定义用于绘图的颜色列表（绿色和红色）。
bars = axes[0].bar(['No Diabetes', 'Diabetes'], target_counts.values, color=colors, alpha=0.8) # 在第一个子图(axes[0])上绘制柱状图。x轴是类别标签，y轴是计数值。
axes[0].set_title('Diabetes Diagnosis Distribution', fontsize=14, fontweight='bold') # 设置第一个子图的标题，并指定字体大小和粗细。
axes[0].set_ylabel('Sample Count') # 设置第一个子图的y轴标签。
# 添加数值标签
for bar, count in zip(bars, target_counts.values): # 遍历每个柱子及其对应的高度值。
    axes[0].text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 5, # 在柱子顶部的中心位置添加文本。
str(count), ha='center', va='bottom', fontweight='bold') # 文本内容是计数值，水平居中(ha)，垂直对齐到底部(va)。

# 饼图
wedges, texts, autotexts = axes[1].pie(target_counts.values, # 在第二个子图(axes[1])上绘制饼图。
                                       labels=['No Diabetes', 'Diabetes'], # 设置每个扇区的标签。
                                       colors=colors, # 设置每个扇区的颜色。
                                       autopct='%1.1f%%', # 设置扇区内显示的百分比格式。
                                       startangle=90, # 设置饼图的起始角度为90度（从顶部开始）。
                                       explode=(0.05, 0)) # 设置第一个扇区（No Diabetes）稍微突出显示。
axes[1].set_title('Diabetes Diagnosis Proportion', fontsize=14, fontweight='bold') # 设置第二个子图的标题。

# 环形图
center_circle = plt.Circle((0, 0), 0.70, fc='white') # 创建一个白色圆形，半径为0.7，用于覆盖饼图中心，形成环形图效果。
axes[2].pie(target_counts.values, labels=['No Diabetes', 'Diabetes'], colors=colors, # 在第三个子图(axes[2])上绘制饼图。
            autopct='%1.1f%%', startangle=90, pctdistance=0.85) # pctdistance=0.85表示百分比标签距离圆心的距离。
axes[2].add_artist(center_circle) # 将创建的白色圆形添加到子图上。
axes[2].set_title('Diabetes Diagnosis Donut Chart', fontsize=14, fontweight='bold') # 设置第三个子图的标题。

plt.tight_layout() # 自动调整子图参数，使其填充整个画布区域，避免标签重叠。
plt.savefig('target_distribution.png', dpi=300, bbox_inches='tight') # 将生成的图表保存为PNG文件，分辨率为300dpi，bbox_inches='tight'确保所有元素都被保存。
plt.show() # 显示图表。

# ==================== 第三部分：特征变量分布分析 ====================
print("\n📈 3. 特征变量分布分析") # 打印第三部分的标题。
print("-" * 40) # 打印分隔符。

# 获取数值特征
numeric_features = data.select_dtypes(include=[np.number]).columns.tolist() # select_dtypes选择数据类型为数值（整数、浮点数）的列，.columns获取列名，.tolist()转为列表。
if'Diagnosis'in numeric_features: # 检查'Diagnosis'是否在数值特征列表中。
    numeric_features.remove('Diagnosis') # 如果在，则将其移除，因为我们想单独分析预测特征。

print(f"数值特征: {numeric_features}") # 打印所有数值型预测特征的列表。

# 统计描述
print("\n📊 特征统计描述:") # 打印子标题。
print(data[numeric_features].describe().round(2)) # 对所有数值特征计算描述性统计，并使用.round(2)将结果四舍五入到小数点后两位。

# 特征分布可视化
n_features = len(numeric_features) # 获取数值特征的数量。
n_cols = 3# 指定我们希望每行显示3个子图。
n_rows = (n_features + n_cols - 1) // n_cols # 计算需要的行数，这是一个向上取整的技巧。

fig, axes = plt.subplots(n_rows, n_cols, figsize=(15, 5 * n_rows)) # 根据计算出的行列数创建子图网格。
axes = axes.flatten() if n_rows > 1 else [axes] if n_cols == 1 else axes # 将axes数组扁平化为一维数组，方便通过单个索引进行遍历。

for i, feature in enumerate(numeric_features): # 遍历所有数值特征及其索引。
# 直方图 + 密度曲线
    sns.histplot(data[feature], kde=True, ax=axes[i], alpha=0.7) # 在对应的子图(axes[i])上绘制直方图和KDE曲线。
    axes[i].set_title(f'{feature} Distribution', fontweight='bold') # 设置子图标题。
    axes[i].set_xlabel(feature) # 设置子图的x轴标签。
    axes[i].set_ylabel('Frequency') # 设置子图的y轴标签。

# 添加统计信息
    mean_val = data[feature].mean() # 计算当前特征的均值。
    median_val = data[feature].median() # 计算当前特征的中位数。
    axes[i].axvline(mean_val, color='red', linestyle='--', label=f'Mean: {mean_val:.2f}') # 在图上绘制一条红色的虚线代表均值。
    axes[i].axvline(median_val, color='green', linestyle='--', label=f'Median: {median_val:.2f}') # 在图上绘制一条绿色的虚线代表中位数。
    axes[i].legend() # 显示图例（包含了均值和中位数的标签）。

# 隐藏多余的子图
for j in range(i + 1, len(axes)): # 遍历最后一个已绘制的子图之后的所有空子图。
    axes[j].set_visible(False) # 将这些多余的子图隐藏起来。

plt.tight_layout() # 自动调整布局。
plt.savefig('feature_distributions.png', dpi=300, bbox_inches='tight') # 保存图表。
plt.show() # 显示图表。